Introducción

Datos

Este conjunto de datos, descargado de Kaggle, contiene una lista exhaustiva de las canciones más famosas de 2023 según Spotify. Ofrece una variedad de características que van más allá de lo que usualmente se encuentra en datasets similares. Proporciona información detallada sobre los atributos de cada canción, su popularidad y su presencia en diversas plataformas musicales. Entre la información incluida se encuentran el nombre de la canción, el nombre del o los artistas, la fecha de lanzamiento, las listas de reproducción y los charts de Spotify, estadísticas de streaming, presencia en Apple Music y Deezer, las posiciones en las listas de Shazam y diversas características de audio.

Está formado por las siguientes variables:

  • track_name: Nombre de la canción

  • artist(s)_name: Nombre del o los artistas de la canción

  • artist_count: Número de artistas que contribuyen a la canción

  • released_year: Año en que se lanzó la canción

  • released_month: Mes en que se lanzó la canción

  • released_day: Día del mes en que se lanzó la canción

  • in_spotify_playlists: Número de listas de reproducción de Spotify en las que está incluida la canción

  • in_spotify_charts: Presencia y posición de la canción en las listas de Spotify

  • streams: Número total de reproducciones en Spotify

  • in_apple_playlists: Número de listas de reproducción de Apple Music en las que está incluida la canción

  • in_apple_charts: Presencia y posición de la canción en las listas de Apple Music

  • in_deezer_playlists: Número de listas de reproducción de Deezer en las que está incluida la canción

  • in_deezer_charts: Presencia y posición de la canción en las listas de Deezer

  • in_shazam_charts: Presencia y posición de la canción en las listas de Shazam

  • bpm: Pulsos por minuto, una medida del tempo de la canción

  • key: Tonalidad de la canción

  • mode: Modo de la canción (mayor o menor)

  • danceability_%: Porcentaje que indica cuán adecuada es la canción para bailar

  • valence_%: Positividad del contenido musical de la canción

  • energy_%: Nivel percibido de energía de la canción

  • acousticness_%: Cantidad de sonido acústico en la canción

  • instrumentalness_%: Cantidad de contenido instrumental en la canción

  • liveness_%: Presencia de elementos de interpretación en vivo

  • speechiness_%: Cantidad de palabras habladas en la canción

Limitaciones

En el contexto del uso de modelos supervisados de aprendizaje automático para la predicción de la popularidad de canciones, es crucial reconocer y abordar diversas limitaciones que pueden influir en la precisión y validez de los resultados obtenidos. A continuación, se discuten algunas de las principales limitaciones identificadas:

  1. Nuevas canciones con mayor número de reproducciones mensuales: Las canciones más recientes tienden a tener un mayor número de reproducciones mensuales en comparación con canciones más antiguas. Este sesgo temporal puede afectar negativamente a los modelos supervisados, dado que estos pueden interpretar la popularidad reciente como una característica intrínseca de la canción, sin considerar la variabilidad temporal en las preferencias de los oyentes. Esto puede llevar a una sobreestimación de la popularidad de las nuevas canciones en comparación con las antiguas.

  2. Popularidad de los artistas: La fama y reconocimiento de un artista contribuyen significativamente a la popularidad de una canción. Canciones de artistas conocidos pueden acumular más reproducciones independientemente de sus características técnicas o artísticas. Esta variable exógena puede sesgar los modelos, haciendo que las predicciones de popularidad se vean influidas más por el renombre del artista que por la calidad o los atributos intrínsecos de la canción.

  3. Influencia de las redes sociales: Algunas canciones pueden mantener o incrementar su popularidad durante períodos más prolongados debido a la influencia de las redes sociales, en lugar de basarse en aspectos técnicos de la música. Campañas virales y tendencias en plataformas como TikTok o Instagram pueden hacer que canciones específicas se mantengan relevantes durante más tiempo, distorsionando así la relación entre las características técnicas de la canción y su popularidad.

  4. Cambios estacionales en la popularidad: La popularidad de ciertas canciones puede ser estacional. Por ejemplo, canciones de verano tienden a perder popularidad al llegar el otoño. Estos patrones estacionales pueden introducir variabilidad adicional en los datos, complicando la capacidad del modelo para hacer predicciones precisas a lo largo de todo el año. Los modelos deben ser capaces de capturar y ajustar estos patrones estacionales para mejorar su precisión.

  5. Insuficiencia de datos: Contar con un conjunto de datos de 800 registros aproximadamente no es suficiente para entrenar modelos supervisados de aprendizaje automático robustos. Una muestra pequeña puede no capturar adecuadamente la diversidad y complejidad del fenómeno estudiado, limitando la capacidad del modelo para hacer predicciones precisas y generalizables.

Importación

La lectura de los datos se realizó utilizando Python. Los datos, provenientes de Kaggle, se descargaron en formato CSV y JSON, y ambos formatos se importarán para su análisis.

import pandas as pd
datos=pd.read_csv("datos/spotify-2023.csv", encoding="latin1",thousands=",")
datos.head()
                            track_name  ... speechiness_%
0  Seven (feat. Latto) (Explicit Ver.)  ...             4
1                                 LALA  ...             4
2                              vampire  ...             6
3                         Cruel Summer  ...            15
4                       WHERE SHE GOES  ...             6

[5 rows x 24 columns]

Tratamiento

Veamos un pequeño resumen de nuestros datos:

datos.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 953 entries, 0 to 952
Data columns (total 24 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   track_name            953 non-null    object 
 1   artist(s)_name        953 non-null    object 
 2   artist_count          953 non-null    int64  
 3   released_year         953 non-null    int64  
 4   released_month        953 non-null    int64  
 5   released_day          953 non-null    int64  
 6   in_spotify_playlists  953 non-null    int64  
 7   in_spotify_charts     953 non-null    int64  
 8   streams               953 non-null    object 
 9   in_apple_playlists    953 non-null    int64  
 10  in_apple_charts       953 non-null    int64  
 11  in_deezer_playlists   953 non-null    int64  
 12  in_deezer_charts      953 non-null    int64  
 13  in_shazam_charts      903 non-null    float64
 14  bpm                   953 non-null    int64  
 15  key                   858 non-null    object 
 16  mode                  953 non-null    object 
 17  danceability_%        953 non-null    int64  
 18  valence_%             953 non-null    int64  
 19  energy_%              953 non-null    int64  
 20  acousticness_%        953 non-null    int64  
 21  instrumentalness_%    953 non-null    int64  
 22  liveness_%            953 non-null    int64  
 23  speechiness_%         953 non-null    int64  
dtypes: float64(1), int64(18), object(5)
memory usage: 178.8+ KB

Podemos observar como la variable “in_shazam_charts” muestra 903 valores no nulos de un total de 953 entradas. Esto significa que hay 50 valores perdidos en esta variable. Lo mismo pasa con la variable “key” tiene 858 valores no nulos de un total de 953 entradas, lo que indica que hay 95 valores perdidos en esta variable.

Para abordar los valores perdidos en estas variables, existen varias estrategias. Una opción es eliminar las filas con valores perdidos si representan una pequeña proporción del conjunto de datos y no afectan significativamente el análisis. Otra opción es imputar valores para los valores perdidos utilizando técnicas como la media, la mediana o la moda, dependiendo de la naturaleza de los datos y el contexto del análisis. Abordaremos nuestro problema con la primera opción:

datos.dropna(inplace=True)

También cabe destacar que la variable “streams” es considerada como tipo “object” en lugar de numérica, lo que sugiere que podría haber valores no numéricos presentes en esta columna. Esto podría deberse a la presencia de caracteres no numéricos, como texto o símbolos, en algunas de las entradas. Veamos que registros no están bien:

datos[datos['streams'].str.isnumeric() == False]["streams"]
574    BPM110KeyAModeMajorDanceability53Valence75Ener...
Name: streams, dtype: object

Eliminemos este registro también:

datos['streams'] = pd.to_numeric(datos['streams'],errors='coerce')
datos = datos.dropna(subset=['streams'])
datos.info()
<class 'pandas.core.frame.DataFrame'>
Index: 816 entries, 0 to 952
Data columns (total 24 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   track_name            816 non-null    object 
 1   artist(s)_name        816 non-null    object 
 2   artist_count          816 non-null    int64  
 3   released_year         816 non-null    int64  
 4   released_month        816 non-null    int64  
 5   released_day          816 non-null    int64  
 6   in_spotify_playlists  816 non-null    int64  
 7   in_spotify_charts     816 non-null    int64  
 8   streams               816 non-null    float64
 9   in_apple_playlists    816 non-null    int64  
 10  in_apple_charts       816 non-null    int64  
 11  in_deezer_playlists   816 non-null    int64  
 12  in_deezer_charts      816 non-null    int64  
 13  in_shazam_charts      816 non-null    float64
 14  bpm                   816 non-null    int64  
 15  key                   816 non-null    object 
 16  mode                  816 non-null    object 
 17  danceability_%        816 non-null    int64  
 18  valence_%             816 non-null    int64  
 19  energy_%              816 non-null    int64  
 20  acousticness_%        816 non-null    int64  
 21  instrumentalness_%    816 non-null    int64  
 22  liveness_%            816 non-null    int64  
 23  speechiness_%         816 non-null    int64  
dtypes: float64(2), int64(18), object(4)
memory usage: 159.4+ KB

Ahora procederemos a convertir todos los elementos del DataFrame que son de tipo entero a tipo flotante con el propósito de facilitar los cálculos numéricos, dado que realizaremos análisis de regresión. Además, transformaremos las columnas ‘key’ y ‘mode’ en variables categóricas y restableceremos el índice del DataFrame para garantizar una organización adecuada de los datos.

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 816 entries, 0 to 815
Data columns (total 24 columns):
 #   Column                Non-Null Count  Dtype   
---  ------                --------------  -----   
 0   track_name            816 non-null    object  
 1   artist(s)_name        816 non-null    object  
 2   artist_count          816 non-null    float64 
 3   released_year         816 non-null    float64 
 4   released_month        816 non-null    float64 
 5   released_day          816 non-null    float64 
 6   in_spotify_playlists  816 non-null    float64 
 7   in_spotify_charts     816 non-null    float64 
 8   streams               816 non-null    float64 
 9   in_apple_playlists    816 non-null    float64 
 10  in_apple_charts       816 non-null    float64 
 11  in_deezer_playlists   816 non-null    float64 
 12  in_deezer_charts      816 non-null    float64 
 13  in_shazam_charts      816 non-null    float64 
 14  bpm                   816 non-null    float64 
 15  key                   816 non-null    category
 16  mode                  816 non-null    category
 17  danceability_%        816 non-null    float64 
 18  valence_%             816 non-null    float64 
 19  energy_%              816 non-null    float64 
 20  acousticness_%        816 non-null    float64 
 21  instrumentalness_%    816 non-null    float64 
 22  liveness_%            816 non-null    float64 
 23  speechiness_%         816 non-null    float64 
dtypes: category(2), float64(20), object(2)
memory usage: 142.5+ KB

Por último comprobaremos si hay regsitros dupiclados en el DataFrame resultante:

duplicados = datos.duplicated(subset=['track_name', 'artist(s)_name'])
registros_duplicados = datos[duplicados]
registros_duplicados
           track_name artist(s)_name  ...  liveness_%  speechiness_%
409  SPIT IN MY FACE!       ThxSoMch  ...        11.0            7.0
512    Take My Breath     The Weeknd  ...        11.0            5.0
646   About Damn Time          Lizzo  ...        34.0            7.0

[3 rows x 24 columns]

Eliminemos los regsitros duplicados y quedemonos con aquel que tenga más “streams”:

datos = datos.sort_values(by='streams', ascending=False).drop_duplicates(subset=['track_name', 'artist(s)_name'], keep='first')

Como podemos observar, la columna “artist(s)_name” contiene los nombres de los artistas participantes en cada canción, los cuales están unidos por comas. Para realizar un análisis individual de cada artista, es necesario separar estos registros y disponerlos de manera independiente. Por lo tanto, se ha creado una nueva versión de los datos denominada “datos_sep” con este propósito:

datos["artist(s)_name"].head(6)
151                      Ed Sheeran
36            Post Malone, Swae Lee
137             Drake, WizKid, Kyla
71     Justin Bieber, The Kid Laroi
122                 Imagine Dragons
610        The Chainsmokers, Halsey
Name: artist(s)_name, dtype: object
datos_sep=datos.copy()
datos_sep["artist(s)_name"]=datos_sep["artist(s)_name"].str.split(", ")
datos_sep=datos_sep.explode("artist(s)_name")
datos_sep = datos_sep.rename(columns={"artist(s)_name": "artist_names"})
column_order = ['track_name', 'artist_names'] + [col for col in datos_sep.columns if col != 'track_name' and col != 'artist_names'] # Para ponerlas en orden
datos_sep = datos_sep[column_order]
datos_sep.reset_index(drop=True, inplace=True)
datos_sep.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1276 entries, 0 to 1275
Data columns (total 24 columns):
 #   Column                Non-Null Count  Dtype   
---  ------                --------------  -----   
 0   track_name            1276 non-null   object  
 1   artist_names          1276 non-null   object  
 2   artist_count          1276 non-null   float64 
 3   released_year         1276 non-null   float64 
 4   released_month        1276 non-null   float64 
 5   released_day          1276 non-null   float64 
 6   in_spotify_playlists  1276 non-null   float64 
 7   in_spotify_charts     1276 non-null   float64 
 8   streams               1276 non-null   float64 
 9   in_apple_playlists    1276 non-null   float64 
 10  in_apple_charts       1276 non-null   float64 
 11  in_deezer_playlists   1276 non-null   float64 
 12  in_deezer_charts      1276 non-null   float64 
 13  in_shazam_charts      1276 non-null   float64 
 14  bpm                   1276 non-null   float64 
 15  key                   1276 non-null   category
 16  mode                  1276 non-null   category
 17  danceability_%        1276 non-null   float64 
 18  valence_%             1276 non-null   float64 
 19  energy_%              1276 non-null   float64 
 20  acousticness_%        1276 non-null   float64 
 21  instrumentalness_%    1276 non-null   float64 
 22  liveness_%            1276 non-null   float64 
 23  speechiness_%         1276 non-null   float64 
dtypes: category(2), float64(20), object(2)
memory usage: 222.4+ KB

Guardado de datos

Tras la lectura y procesamiento de los datos, estos fueron almacenados en archivos feather para su posterior uso en R. Se usó esta extensión porque es extremadamente rápido para leer y escribir dato. Además, este formato es compatible con múltiples lenguajes de programación, principalmente Python y R, lo que facilita la colaboración entre diferentes equipos. Se generaron dos variantes: Spotify.feather y Spotify_sep.feather.

import pyarrow.feather as feather
feather.write_feather(datos, 'datos.feather',compression="uncompressed")
feather.write_feather(datos_sep, 'datos_sep.feather',compression="uncompressed")

Global

Fila 1

Cuadro

816

Cuadro

570

Cuadro

468.99

Cuadro

C#

Cuadro

122

Fila 2

Fila 3

Top 10

Col

Shiny applications not supported in static R Markdown documents

Elementos comunes

Col

Elementos comunes

Selección de las variables y renombramos todas las columnas que contienen el patrón _% eliminando ese patrón del nombre de la columna

datos_modelo_reg=datos %>%
  select(-c("released_year",
            "released_month",
            "released_day",
            "track_name", 
            "artist(s)_name")) %>% 
  rename_with(~ str_replace(., "_%", ""), contains("_%"))

División de datos en conjuntos de entrenamiento y prueba

particion_reg = datos_modelo_reg %>% 
  initial_split(prop = 0.8)

datos_reg_ent= particion_reg %>% training()

datos_reg_test= particion_reg %>% testing()

Creamos una receta de preprocesamiento de datos

preprocesado_reg=recipe(streams ~ .,datos_modelo_reg) %>%
  step_novel(all_nominal()) %>%
  step_dummy(all_nominal()) %>%
  step_zv(all_predictors()) %>% 
  step_normalize(all_predictors())

Creamos un objeto para realizar validación cruzada

cv_folds_reg=vfold_cv(datos_reg_ent, v = 10)

Col

Streams

Regresión lineal

Col

Regresión lineal

Especificamos el tipo de modelos que queremos usar junto con el motor

lm_reg_spec=linear_reg() %>% 
  set_engine("lm") %>% 
  set_mode("regression")

Creamos un workflow, añadiendole el modelo y el preprocesado

lm_reg_wf=
  workflow() %>% 
  add_model(lm_reg_spec) %>% 
  add_recipe(preprocesado_reg) 

Ajusta el flujo de trabajo utilizando validación cruzada con los folds definidos anteriormente

resultados_lm_reg_cv = lm_reg_wf %>% 
  fit_resamples(
    resamples = cv_folds_reg
  )

“Recogemos” las metricas de la validación cruzada

resultados_lm_reg_cv %>%
  collect_metrics()
# A tibble: 2 × 6
  .metric .estimator          mean     n       std_err .config             
  <chr>   <chr>              <dbl> <int>         <dbl> <chr>               
1 rmse    standard   289709710.       10 14591076.     Preprocessor1_Model1
2 rsq     standard           0.714    10        0.0298 Preprocessor1_Model1

Validamos nuestro modelo con nuestro conjunto de prueba

modelo_lm_reg=lm_reg_wf %>%
  last_fit(particion_reg)
modelo_lm_reg %>% 
  collect_metrics()
# A tibble: 2 × 4
  .metric .estimator     .estimate .config             
  <chr>   <chr>              <dbl> <chr>               
1 rmse    standard   307314002.    Preprocessor1_Model1
2 rsq     standard           0.631 Preprocessor1_Model1

Col

Importancia de las variables

Bosques aleatorios de regresión

Col

Bosques aleatorios de regresión

Especificación para un modelo de bosque aleatorio donde los hiperparámetros que se configuran para ser ajustados mediante tune()

rf_reg_spec=rand_forest(trees=tune(),
                    min_n=tune()) %>% 
  set_engine("randomForest") %>% 
  set_mode("regression")

Creamos el workflow

rf_reg_wf=
  workflow() %>% 
  add_model(rf_reg_spec) %>% 
  add_recipe(preprocesado_reg) 

Creamos una cuadrícula de hiperparámetros para el tune()

grid_rf_reg = expand_grid(trees=seq(100,200,by=50),
                           min_n=seq(2,8,by=2))

Ajustamos el flujo de trabajo utilizando validación cruzada con los folds definidos anteriormente

resultados_tune_rf_reg = rf_reg_wf %>% 
  tune_grid(
    resamples = cv_folds_reg,
    grid = grid_rf_reg)
resultados_tune_rf_reg %>%
  collect_metrics()
# A tibble: 24 × 8
   trees min_n .metric .estimator          mean     n       std_err .config     
   <dbl> <dbl> <chr>   <chr>              <dbl> <int>         <dbl> <chr>       
 1   100     2 rmse    standard   256343034.       10 18454186.     Preprocesso…
 2   100     2 rsq     standard           0.774    10        0.0272 Preprocesso…
 3   100     4 rmse    standard   252139270.       10 18313961.     Preprocesso…
 4   100     4 rsq     standard           0.780    10        0.0257 Preprocesso…
 5   100     6 rmse    standard   257069403.       10 17452155.     Preprocesso…
 6   100     6 rsq     standard           0.772    10        0.0261 Preprocesso…
 7   100     8 rmse    standard   255818736.       10 18534822.     Preprocesso…
 8   100     8 rsq     standard           0.776    10        0.0272 Preprocesso…
 9   150     2 rmse    standard   252605128.       10 19261198.     Preprocesso…
10   150     2 rsq     standard           0.780    10        0.0274 Preprocesso…
# ℹ 14 more rows

Seleccionamos la combinación de hiperparámetros que maximiza el coeficiente de determinación (el mejor modelo)

rf_reg_mejor=resultados_tune_rf_reg %>%
  select_best(metric="rsq")

Actualizamos el flujo de trabajo con los mejores hiperparámetros seleccionados

rf_reg_wfl_final = rf_reg_wf %>% 
  finalize_workflow(rf_reg_mejor)

Validamos nuestro modelo con nuestro conjunto de prueba

modelo_rf_reg_final = rf_reg_wfl_final %>%
  last_fit(particion_reg)
modelo_rf_reg_final %>% 
  collect_metrics() 
# A tibble: 2 × 4
  .metric .estimator     .estimate .config             
  <chr>   <chr>              <dbl> <chr>               
1 rmse    standard   205032558.    Preprocessor1_Model1
2 rsq     standard           0.829 Preprocessor1_Model1

Col

Importancia de las variables

Máquinas de soporte vectorial

Col

Máquinas de soporte vectorial

svm_reg_spec=svm_rbf(cost=tune(),rbf_sigma=tune()) %>% 
  set_engine("kernlab") %>% 
  set_mode("regression")

svm_reg_wf=
  workflow() %>% 
  add_model(svm_reg_spec) %>% 
  add_recipe(preprocesado_reg) 

grid_svm_reg = expand_grid(cost = 3^(1:4), rbf_sigma = 3^(-5:-2))

resultados_tune_svm_reg = svm_reg_wf %>% 
  tune_grid(
    resamples = cv_folds_reg,
    grid = grid_svm_reg)
resultados_tune_svm_reg %>%
  collect_metrics()
# A tibble: 32 × 8
    cost rbf_sigma .metric .estimator          mean     n       std_err .config 
   <dbl>     <dbl> <chr>   <chr>              <dbl> <int>         <dbl> <chr>   
 1     3   0.00412 rmse    standard   267988672.       10 21177726.     Preproc…
 2     3   0.00412 rsq     standard           0.752    10        0.0277 Preproc…
 3     3   0.0123  rmse    standard   272498654.       10 17285561.     Preproc…
 4     3   0.0123  rsq     standard           0.743    10        0.0242 Preproc…
 5     3   0.0370  rmse    standard   305014702.       10 15267451.     Preproc…
 6     3   0.0370  rsq     standard           0.682    10        0.0179 Preproc…
 7     3   0.111   rmse    standard   399663092.       10 23991324.     Preproc…
 8     3   0.111   rsq     standard           0.474    10        0.0202 Preproc…
 9     9   0.00412 rmse    standard   267577026.       10 18819288.     Preproc…
10     9   0.00412 rsq     standard           0.750    10        0.0274 Preproc…
# ℹ 22 more rows
svm_reg_mejor=resultados_tune_svm_reg %>%
  select_best(metric="rsq")

svm_reg_wfl_final = svm_reg_wf %>% 
  finalize_workflow(svm_reg_mejor)

modelo_svm_reg_final = svm_reg_wfl_final %>%
  last_fit(particion_reg)
modelo_svm_reg_final %>% 
  collect_metrics() 
# A tibble: 2 × 4
  .metric .estimator     .estimate .config             
  <chr>   <chr>              <dbl> <chr>               
1 rmse    standard   237761197.    Preprocessor1_Model1
2 rsq     standard           0.694 Preprocessor1_Model1

Redes neuronales

Col

Redes neuronales

Véase el RedesNeuronales_reg.ipynb, en la carpeta modelos > regresion.

Elementos comunes

Col

Elementos comunes

datos_modelo_class = datos %>%
  rename_with(~ str_replace(., "_%", ""), contains("_%")) %>%
  select(-c(1:16))

particion_class = initial_split(datos_modelo_class, prop = 0.8)

datos_class_ent = training(particion_class)

datos_class_test = testing(particion_class)

preprocesado_class = recipe(mode ~ ., datos_modelo_class) %>%
  step_zv(all_numeric()) %>%
  step_normalize(all_numeric())

cv_folds_class = vfold_cv(datos_class_ent, v = 10, strata = mode)

Clases balanceadas

Col

Gráfico multivariante de las variables

Regresión logística

Col

Regresión logística

log_class_spec = logistic_reg() %>%
  set_engine("glm") %>%
  set_mode("classification")

log_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(log_class_spec)

resultados_log_class_cv = log_class_wfl %>% 
  fit_resamples(
    resamples = cv_folds_class,
    metrics = metric_set(roc_auc, accuracy)
    )
resultados_log_class_cv %>%
  collect_metrics()
# A tibble: 2 × 6
  .metric  .estimator  mean     n std_err .config             
  <chr>    <chr>      <dbl> <int>   <dbl> <chr>               
1 accuracy binary     0.551    10  0.0166 Preprocessor1_Model1
2 roc_auc  binary     0.576    10  0.0224 Preprocessor1_Model1
modelo_log_class_final=log_class_wfl %>% 
  last_fit(particion_class)
modelo_log_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
          Truth
Prediction Major Minor
     Major    59    40
     Minor    37    28
modelo_log_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
# A tibble: 2 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary        0.530 
2 kap      binary        0.0265

Col

Importancia de las variables

Curva ROC

Máquinas de soporte vectorial

Col

Máquinas de soporte vectorial

svm_class_spec = svm_rbf(cost = tune(), rbf_sigma = tune()) %>%
  set_engine("kernlab") %>%
  set_mode("classification")

svm_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(svm_class_spec)

grid_svm_class = expand_grid(cost = 10^seq(-3, 2, length.out = 5), 
                             rbf_sigma = 10^seq(-3, 0, length.out = 5))

resultados_svm_class_tune = svm_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_svm_class,
    metrics = metric_set(roc_auc, accuracy)
  )
resultados_svm_class_tune %>%
  collect_metrics()
# A tibble: 50 × 8
    cost rbf_sigma .metric  .estimator  mean     n std_err .config              
   <dbl>     <dbl> <chr>    <chr>      <dbl> <int>   <dbl> <chr>                
 1 0.001   0.001   accuracy binary     0.544    10 0.00102 Preprocessor1_Model01
 2 0.001   0.001   roc_auc  binary     0.582    10 0.0185  Preprocessor1_Model01
 3 0.001   0.00562 accuracy binary     0.544    10 0.00102 Preprocessor1_Model02
 4 0.001   0.00562 roc_auc  binary     0.574    10 0.0201  Preprocessor1_Model02
 5 0.001   0.0316  accuracy binary     0.544    10 0.00102 Preprocessor1_Model03
 6 0.001   0.0316  roc_auc  binary     0.564    10 0.0197  Preprocessor1_Model03
 7 0.001   0.178   accuracy binary     0.544    10 0.00102 Preprocessor1_Model04
 8 0.001   0.178   roc_auc  binary     0.568    10 0.0173  Preprocessor1_Model04
 9 0.001   1       accuracy binary     0.544    10 0.00102 Preprocessor1_Model05
10 0.001   1       roc_auc  binary     0.560    10 0.0237  Preprocessor1_Model05
# ℹ 40 more rows
svm_class_mejor=resultados_svm_class_tune %>%
  select_best(metric="accuracy")

svm_class_wfl_final = svm_class_wfl %>% 
  finalize_workflow(svm_class_mejor)

modelo_svm_class_final = svm_class_wfl_final %>%
  last_fit(particion_class)
modelo_svm_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
          Truth
Prediction Major Minor
     Major    86    57
     Minor    10    11
modelo_svm_class_final %>% 
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
# A tibble: 2 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary        0.591 
2 kap      binary        0.0641
          Truth
Prediction Major Minor
     Major    86    57
     Minor    10    11

Col

Curva ROC

Bosques aleatorios de clasificación

Col

Bosques aleatorios de clasificación

rf_class_spec = rand_forest(
  mtry = tune(),
  min_n = tune(),
  trees = 1000  # Número de árboles fijo
) %>%
  set_engine("randomForest") %>%
  set_mode("classification")

rf_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(rf_class_spec)

grid_rf_class = expand_grid(
  mtry = seq(2, ncol(datos_class_ent) - 1, by = 2), 
  min_n = seq(2, 22, by = 4))

resultados_tune_rf_class = rf_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_rf_class,
    metrics = metric_set(roc_auc, accuracy)
)
resultados_tune_rf_class %>%
  collect_metrics()
# A tibble: 36 × 8
    mtry min_n .metric  .estimator  mean     n std_err .config              
   <dbl> <dbl> <chr>    <chr>      <dbl> <int>   <dbl> <chr>                
 1     2     2 accuracy binary     0.538    10  0.0206 Preprocessor1_Model01
 2     2     2 roc_auc  binary     0.545    10  0.0229 Preprocessor1_Model01
 3     2     6 accuracy binary     0.539    10  0.0190 Preprocessor1_Model02
 4     2     6 roc_auc  binary     0.538    10  0.0220 Preprocessor1_Model02
 5     2    10 accuracy binary     0.534    10  0.0152 Preprocessor1_Model03
 6     2    10 roc_auc  binary     0.541    10  0.0190 Preprocessor1_Model03
 7     2    14 accuracy binary     0.526    10  0.0181 Preprocessor1_Model04
 8     2    14 roc_auc  binary     0.545    10  0.0185 Preprocessor1_Model04
 9     2    18 accuracy binary     0.534    10  0.0157 Preprocessor1_Model05
10     2    18 roc_auc  binary     0.543    10  0.0191 Preprocessor1_Model05
# ℹ 26 more rows
rf_class_mejor=resultados_tune_rf_class %>%
  select_best(metric="accuracy")

rf_class_wfl_final = rf_class_wfl %>% 
  finalize_workflow(rf_class_mejor)

modelo_rf_class_final = rf_class_wfl_final %>%
  last_fit(particion_class)
modelo_rf_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
# A tibble: 2 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary         0.585
2 kap      binary         0.149
modelo_rf_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
          Truth
Prediction Major Minor
     Major    61    33
     Minor    35    35

Col

Importancia de las variables

Curva ROC

K-nearest neighbors

Col

K-nearest neighbors

knn_class_spec = nearest_neighbor(
  neighbors = tune(),       
  weight_func = tune(),     
  dist_power = tune()       
) %>%
  set_engine("kknn") %>%
  set_mode("classification")

knn_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(knn_class_spec)

grid_knn_class = expand_grid(
  neighbors = seq(1, 20, by = 2),
  weight_func = c("gaussian", "optimal"),
  dist_power = 1:2
)

resultados_tune_knn_class = knn_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_knn_class,
    metrics = metric_set(roc_auc, accuracy)
)
resultados_tune_knn_class %>%
  collect_metrics()
# A tibble: 80 × 9
   neighbors weight_func dist_power .metric  .estimator  mean     n std_err
       <dbl> <chr>            <dbl> <chr>    <chr>      <dbl> <int>   <dbl>
 1         1 gaussian             1 accuracy binary     0.506    10  0.0160
 2         1 gaussian             1 roc_auc  binary     0.501    10  0.0167
 3         3 gaussian             1 accuracy binary     0.540    10  0.0240
 4         3 gaussian             1 roc_auc  binary     0.528    10  0.0224
 5         5 gaussian             1 accuracy binary     0.554    10  0.0202
 6         5 gaussian             1 roc_auc  binary     0.537    10  0.0230
 7         7 gaussian             1 accuracy binary     0.559    10  0.0255
 8         7 gaussian             1 roc_auc  binary     0.548    10  0.0274
 9         9 gaussian             1 accuracy binary     0.548    10  0.0227
10         9 gaussian             1 roc_auc  binary     0.543    10  0.0281
# ℹ 70 more rows
# ℹ 1 more variable: .config <chr>
knn_class_mejor=resultados_tune_knn_class %>%
  select_best(metric="accuracy")

knn_class_wfl_final = knn_class_wfl %>% 
  finalize_workflow(knn_class_mejor)

modelo_knn_class_final = knn_class_wfl_final %>%
  last_fit(particion_class)
modelo_knn_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
          Truth
Prediction Major Minor
     Major    63    35
     Minor    33    33
modelo_knn_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
# A tibble: 2 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary         0.585
2 kap      binary         0.142

Col

Curva ROC

---
title: "Spotify"
logo: "complementario/logo.png"
output:
  flexdashboard::flex_dashboard:
    css: "complementario/layout.css"
    orientation: columns
    source_code: embed
    theme:
      version: 4
      bg: "#FFFFFF"
      fg: "#000000"
      primary: "#1DB954"
      navbar-bg: "#292637"
      base_font: 
        google: Prompt
      heading_font:
        google: Sen
      code_font:
        google: 
          # arguments to sass::font_google() 
          family: JetBrains Mono
          local: false
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(tidyverse)
library(knitr)
```

# Introducción {.storyboard}

### Datos 

Este conjunto de datos, descargado de Kaggle, contiene una lista exhaustiva de las canciones más famosas de 2023 según Spotify. Ofrece una variedad de características que van más allá de lo que usualmente se encuentra en datasets similares. Proporciona información detallada sobre los atributos de cada canción, su popularidad y su presencia en diversas plataformas musicales. Entre la información incluida se encuentran el nombre de la canción, el nombre del o los artistas, la fecha de lanzamiento, las listas de reproducción y los charts de Spotify, estadísticas de streaming, presencia en Apple Music y Deezer, las posiciones en las listas de Shazam y diversas características de audio.

Está formado por las siguientes variables:

- **track_name**: Nombre de la canción

- <strong>artist(s)_name</strong>: Nombre del o los artistas de la canción

- **artist_count**: Número de artistas que contribuyen a la canción
- **released_year**: Año en que se lanzó la canción
- **released_month**: Mes en que se lanzó la canción
- **released_day**: Día del mes en que se lanzó la canción
- **in_spotify_playlists**: Número de listas de reproducción de Spotify en las que está incluida la canción
- **in_spotify_charts**: Presencia y posición de la canción en las listas de Spotify
- **streams**: Número total de reproducciones en Spotify
- **in_apple_playlists**: Número de listas de reproducción de Apple Music en las que está incluida la canción
- **in_apple_charts**: Presencia y posición de la canción en las listas de Apple Music
- **in_deezer_playlists**: Número de listas de reproducción de Deezer en las que está incluida la canción
- **in_deezer_charts**: Presencia y posición de la canción en las listas de Deezer
- **in_shazam_charts**: Presencia y posición de la canción en las listas de Shazam
- **bpm**: Pulsos por minuto, una medida del tempo de la canción
- **key**: Tonalidad de la canción
- **mode**: Modo de la canción (mayor o menor)
- **danceability_%**: Porcentaje que indica cuán adecuada es la canción para bailar
- **valence_%**: Positividad del contenido musical de la canción
- **energy_%**: Nivel percibido de energía de la canción
- **acousticness_%**: Cantidad de sonido acústico en la canción
- **instrumentalness_%**: Cantidad de contenido instrumental en la canción
- **liveness_%**: Presencia de elementos de interpretación en vivo
- **speechiness_%**: Cantidad de palabras habladas en la canción

### Limitaciones

En el contexto del uso de modelos supervisados de aprendizaje automático para la predicción de la popularidad de canciones, es crucial reconocer y abordar diversas limitaciones que pueden influir en la precisión y validez de los resultados obtenidos. A continuación, se discuten algunas de las principales limitaciones identificadas:

1. **Nuevas canciones con mayor número de reproducciones mensuales**:
   Las canciones más recientes tienden a tener un mayor número de reproducciones mensuales en comparación con canciones más antiguas. Este sesgo temporal puede afectar negativamente a los modelos supervisados, dado que estos pueden interpretar la popularidad reciente como una característica intrínseca de la canción, sin considerar la variabilidad temporal en las preferencias de los oyentes. Esto puede llevar a una sobreestimación de la popularidad de las nuevas canciones en comparación con las antiguas.

2. **Popularidad de los artistas**:
   La fama y reconocimiento de un artista contribuyen significativamente a la popularidad de una canción. Canciones de artistas conocidos pueden acumular más reproducciones independientemente de sus características técnicas o artísticas. Esta variable exógena puede sesgar los modelos, haciendo que las predicciones de popularidad se vean influidas más por el renombre del artista que por la calidad o los atributos intrínsecos de la canción.

3. **Influencia de las redes sociales**:
   Algunas canciones pueden mantener o incrementar su popularidad durante períodos más prolongados debido a la influencia de las redes sociales, en lugar de basarse en aspectos técnicos de la música. Campañas virales y tendencias en plataformas como TikTok o Instagram pueden hacer que canciones específicas se mantengan relevantes durante más tiempo, distorsionando así la relación entre las características técnicas de la canción y su popularidad.

4. **Cambios estacionales en la popularidad**:
   La popularidad de ciertas canciones puede ser estacional. Por ejemplo, canciones de verano tienden a perder popularidad al llegar el otoño. Estos patrones estacionales pueden introducir variabilidad adicional en los datos, complicando la capacidad del modelo para hacer predicciones precisas a lo largo de todo el año. Los modelos deben ser capaces de capturar y ajustar estos patrones estacionales para mejorar su precisión.

5. **Insuficiencia de datos**:
   Contar con un conjunto de datos de 800 registros aproximadamente no es suficiente para entrenar modelos supervisados de aprendizaje automático robustos. Una muestra pequeña puede no capturar adecuadamente la diversidad y complejidad del fenómeno estudiado, limitando la capacidad del modelo para hacer predicciones precisas y generalizables.

### Importación

La lectura de los datos se realizó utilizando Python. Los datos, provenientes de Kaggle, se descargaron en formato CSV y JSON, y ambos formatos se importarán para su análisis.

```{python echo=TRUE}
import pandas as pd
datos=pd.read_csv("datos/spotify-2023.csv", encoding="latin1",thousands=",")
datos.head()
```

### Tratamiento

Veamos un pequeño resumen de nuestros datos:

```{python echo=TRUE}
datos.info()
```

Podemos observar como la variable "in_shazam_charts" muestra 903 valores no nulos de un total de 953 entradas. Esto significa que hay 50 valores perdidos en esta variable. Lo mismo pasa con la variable "key" tiene 858 valores no nulos de un total de 953 entradas, lo que indica que hay 95 valores perdidos en esta variable.

Para abordar los valores perdidos en estas variables, existen varias estrategias. Una opción es eliminar las filas con valores perdidos si representan una pequeña proporción del conjunto de datos y no afectan significativamente el análisis. Otra opción es imputar valores para los valores perdidos utilizando técnicas como la media, la mediana o la moda, dependiendo de la naturaleza de los datos y el contexto del análisis. Abordaremos nuestro problema con la primera opción:

```{python echo=TRUE}
datos.dropna(inplace=True)
```

También cabe destacar que la variable "streams" es considerada como tipo "object" en lugar de numérica, lo que sugiere que podría haber valores no numéricos presentes en esta columna. Esto podría deberse a la presencia de caracteres no numéricos, como texto o símbolos, en algunas de las entradas. Veamos que registros no están bien:

```{python echo=TRUE}
datos[datos['streams'].str.isnumeric() == False]["streams"]
```

Eliminemos este registro también:

```{python echo=TRUE}
datos['streams'] = pd.to_numeric(datos['streams'],errors='coerce')
datos = datos.dropna(subset=['streams'])
datos.info()
```

Ahora procederemos a convertir todos los elementos del DataFrame que son de tipo entero a tipo flotante con el propósito de facilitar los cálculos numéricos, dado que realizaremos análisis de regresión. Además, transformaremos las columnas 'key' y 'mode' en variables categóricas y restableceremos el índice del DataFrame para garantizar una organización adecuada de los datos.

```{python}
int_columns = datos.select_dtypes(include=['int']).columns
datos[int_columns] = datos[int_columns].astype(float)
datos['key'] = datos['key'].astype('category')
datos['mode'] = datos['mode'].astype('category')
datos.reset_index(drop=True, inplace=True)
datos.info()
```

Por último comprobaremos si hay regsitros dupiclados en el DataFrame resultante:

```{python echo=TRUE}
duplicados = datos.duplicated(subset=['track_name', 'artist(s)_name'])
registros_duplicados = datos[duplicados]
registros_duplicados
```

Eliminemos los regsitros duplicados y quedemonos con aquel que tenga más "streams":

```{python echo=TRUE}
datos = datos.sort_values(by='streams', ascending=False).drop_duplicates(subset=['track_name', 'artist(s)_name'], keep='first')
```

Como podemos observar, la columna "artist(s)_name" contiene los nombres de los artistas participantes en cada canción, los cuales están unidos por comas. Para realizar un análisis individual de cada artista, es necesario separar estos registros y disponerlos de manera independiente. Por lo tanto, se ha creado una nueva versión de los datos denominada "datos_sep" con este propósito:

```{python echo=TRUE}
datos["artist(s)_name"].head(6)
datos_sep=datos.copy()
datos_sep["artist(s)_name"]=datos_sep["artist(s)_name"].str.split(", ")
datos_sep=datos_sep.explode("artist(s)_name")
datos_sep = datos_sep.rename(columns={"artist(s)_name": "artist_names"})
column_order = ['track_name', 'artist_names'] + [col for col in datos_sep.columns if col != 'track_name' and col != 'artist_names'] # Para ponerlas en orden
datos_sep = datos_sep[column_order]
datos_sep.reset_index(drop=True, inplace=True)
datos_sep.info()
```

### Guardado de datos

Tras la lectura y procesamiento de los datos, estos fueron almacenados en archivos `feather` para su posterior uso en R. Se usó esta extensión porque es extremadamente rápido para leer y escribir dato. Además, este formato es compatible con múltiples lenguajes de programación, principalmente Python y R, lo que facilita la colaboración entre diferentes equipos. Se generaron dos variantes: `Spotify.feather` y `Spotify_sep.feather`. 

```{python eval=FALSE, echo=TRUE}
import pyarrow.feather as feather
feather.write_feather(datos, 'datos.feather',compression="uncompressed")
feather.write_feather(datos_sep, 'datos_sep.feather',compression="uncompressed")
```

# Global {data-navmenu="Resumen" data-orientation=rows}

## Fila 1 

### Cuadro

```{r}
rm(list=ls())
library(shiny)
library(bslib)
library(DT)
library(arrow)
library(tidyverse)
library(tidymodels)
library(plotly)
library(highcharter)
library(ggplot2)
datos=read_feather("datos/datos.feather")
valueBox(nrow(datos), 
         caption="Número de canciones", 
         icon="fa-music",
         color="#00B0F6")
```

### Cuadro

```{r}
valueBox(length(unique(datos$`artist(s)_name`)), 
         caption="Artistas", 
         icon="fa-person",
         color="#F8766D")
```

### Cuadro

```{r}
valueBox(round(mean(datos$streams)/1000000,2), 
         caption="millones de streams de media", 
         icon="fa-play",
         color="#E76BF3")
```

### Cuadro

```{r}
valueBox(names(table(datos$key))[which.max(table(datos$key))], 
         caption="es la tonalidad más usada", 
         icon="fa-medium",
         color="#FFB558")
```

### Cuadro

```{r}
valueBox(trunc(mean(datos$bpm)), 
         caption= "pulsos por minuto de media", 
         icon="fa-gauge-high",
         color="#00BF7D")
```

## Fila 2

### 

```{r}
datos %>% 
  arrange(desc(streams)) %>% 
  slice(1:50) %>% 
  hchart(
    type = "treemap", 
    hcaes(x = track_name, value = streams, color = streams)
  ) %>%
  hc_colorAxis(stops = color_stops(colors = c("white", "#1DB954"))) %>%
  hc_title(text = "Top 50 Canciones por streams") 
```

###

```{r}
datos %>%
  count(mode) %>% 
  hchart("column", hcaes(x = mode, y = n)) %>%
  hc_title(text = "") %>%
  hc_subtitle(text = "") %>%
  hc_yAxis(title = list(text = "")) %>%
  hc_xAxis(title = list(text = "")) %>%
  hc_legend(enabled = FALSE) %>%
  hc_chart(zoomType = "x") %>%
  hc_colors(c("#7CB5EC")) 
```


### 

```{r}
datos$streams=as.double(datos$streams)
grafico3=datos %>% 
  mutate(artist_count=factor(artist_count)) %>% 
  ggplot()+
  geom_boxplot(aes(x=artist_count,y=streams,fill=artist_count)) +
  labs(title = "", 
       y = "Streams",x="Número de artistas") +
  theme_minimal() +
  theme(legend.position = "none")
ggplotly(grafico3)
```


## Fila 3 

### 

```{r}
datos_resumen= datos %>%
  mutate(year_group = case_when(
    released_year >= 2000 & released_year <= 2009 ~ "2000s",
    released_year >= 2010 & released_year <= 2019 ~ "2010s",
    released_year == 2020 ~ "2020",
    released_year == 2021 ~ "2021",
    released_year == 2022 ~ "2022",
    released_year == 2023 ~ "2023",
    TRUE ~ "Antes del 2000"  
  )) %>%
  mutate(year_group = factor(year_group, levels = c("Antes del 2000", "2000s", "2010s", "2020", "2021", "2022", "2023"), ordered = TRUE))

nombres_meses=c(
  "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
  "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
)
todos_los_meses =data.frame(
  Mes_Numero = 1:12,
  Mes_Nombre = factor(nombres_meses, levels = nombres_meses, ordered = TRUE)
)
datos_resumen=inner_join(datos_resumen, todos_los_meses,
           by = c("released_month"="Mes_Numero"))
grafico4=datos_resumen %>% 
  ggplot(aes(x = Mes_Nombre, fill = year_group)) +
  geom_bar() +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(fill="Año",x="",y="",title="Número de canciones por mes")
ggplotly(grafico4)
```

###

```{r}
datos_sep=read_feather("datos/datos_sep.feather")
datos_sep=datos_sep %>% 
  rename_with(~ str_replace(., "_%", ""), contains("_%"))
datos_sep %>% 
  group_by(artist_names) %>% 
  summarise(Total=n()) %>% 
  arrange(desc(Total)) %>% 
  slice(1:10) %>% 
  hchart(
    "pie", hcaes(x = artist_names, y = Total),
    name = "Canciones"
    ) 
```

###

```{r}
library(DT)
datos %>% 
  select(track_name,`artist(s)_name`,released_year,streams) %>% 
  arrange(desc(streams)) %>% 
  datatable(options=list(pageLength = 5))
```

# Top 10 {data-navmenu="Resumen"}

## Col

###

```{r echo=FALSE}
Top10Artistas=datos_sep %>% 
  group_by(artist_names) %>% 
  summarise(Total=n()) %>% 
  arrange(desc(Total)) %>% 
  slice(1:10) %>% 
  pull(artist_names)
DatosTop10Artistas=datos_sep%>% 
  group_by(artist_names,released_month) %>% 
  summarise(Canciones=n()) %>% 
  filter(artist_names %in% Top10Artistas) %>% 
  ungroup() %>% 
  complete(artist_names, released_month,fill = list(Canciones = 0))
DatosTop10Artistas=inner_join(DatosTop10Artistas, todos_los_meses,
           by = c("released_month"="Mes_Numero")) %>% 
  mutate(Canciones = replace_na(Canciones, 0))
max_canciones=max(DatosTop10Artistas$Canciones)

library(shiny)
library(flexdashboard)
library(ggplot2)
library(plotly)
library(highcharter)
library(tidyverse)
library(DT)

shinyApp(
  ui = fluidPage(
    titlePanel("Resumen por artista (Top 10)"),
    page_sidebar(
                 sidebar = sidebar(
                   selectInput("artista", "Selecciona un Artista:",
                               choices = Top10Artistas)
                 ),
                 textOutput("texto_acumulado"),
                 fluidRow(
                   style = "height: 200px;",
                   plotlyOutput("grafico_meses",height = 200),
                 ),
                 fluidRow(
                   style = "height: 300px;",
                   column(6,
                     plotlyOutput("grafico_variables",height = 300),
                   ),
                   column(6,
                     plotlyOutput("grafico_modo",height = 300),
                   )
                 ),
                 fluidRow(
                   style = "height: 310px;",
                   column(8,
                     highchartOutput("grafico_tree",height=300),
                   ),
                   column(4,
                      highchartOutput("grafico_tarta",height=300)   
                  )
                 ),
                 fluidRow(
                   column(12,
                          DTOutput("tabla_musica",height=400)
                          )
                 )
    )
  ),
  server = function(input, output) {
    datosFiltrados = reactive({
      datos_sep %>% 
        filter(artist_names == input$artista)
    })
    
    output$texto_acumulado = renderText({
      dato_streams_total = datosFiltrados() %>% 
        group_by(artist_names) %>% 
        summarise(total_rep = sum(streams)) %>% 
        pull(total_rep)
      paste("Acumula", round(dato_streams_total / 1000000, 2), "millones de streams")
    })
    
    output$grafico_meses = renderPlotly({
      DatosTop10Artistas %>%
        filter(artist_names == input$artista) %>%
        ggplot(aes(x = Mes_Nombre, y = Canciones, fill = Mes_Nombre)) +
        geom_bar(stat = "identity") +
        labs(x = "", y = "", title = paste("Número de canciones de", input$artista, "por mes")) +
        theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
        ylim(0, max_canciones) +
        guides(fill = "none") +
        theme_minimal()
    })
    
    output$grafico_variables = renderPlotly({
      datosFiltrados() %>% 
        select(-(1:(ncol(datos_sep) - 7))) %>% 
        pivot_longer(everything(), names_to = "Variable", values_to = "Value") %>% 
        ggplot(aes(x = Value, fill = Variable, y = after_stat(scaled))) +
        geom_density(alpha = 0.5) +  
        theme_minimal() +  
        labs(title = paste("Distribución de las variables de", input$artista),
             x = "Valor (%)", y = "") +
        theme_minimal() 
    })
    
    output$grafico_modo = renderPlotly({
      datosFiltrados() %>% 
        select(mode) %>% 
        ggplot(aes(x = mode, fill = mode)) +
        geom_bar(color = "black") +
        labs(title = paste("Modo de canciones de", input$artista),
             x = "",
             y = "") +
        theme_minimal() +
        theme(legend.position = "none")
    })
    output$tabla_musica=renderDT({
      datos_sep %>%
        filter(artist_names == input$artista) %>% 
        select(track_name,artist_names,released_year,streams) %>% 
                 arrange(desc(streams)) %>%
                 datatable(options=list(pageLength = 10))
    })
    output$grafico_tree=renderHighchart({
      datos_sep %>% 
        filter(artist_names==input$artista) %>% 
        hchart(
          type = "treemap", 
          hcaes(x = track_name, value = streams, color = streams)
          ) %>%
        hc_colorAxis(stops = color_stops(colors = c("white", "#1DB954"))) %>%
        hc_title(text = "Top canciones por streams") 
    })
    output$grafico_tarta=renderHighchart({
      datos_sep %>% 
  filter(artist_names=="Taylor Swift") %>% 
  group_by(key) %>% 
  summarise(Total=n()) %>% 
  arrange(key) %>% 
  hchart(
    "pie", hcaes(x = key, y = Total),
    name = "Canciones"
  ) %>% 
        hc_title(text="Keys más usadas")
    })
  }
)

```


# Elementos comunes {data-navmenu="Regresión"}

## Col

### Elementos comunes

```{r}
load("complementario/Modelos_reg.RData")
```

Selección de las variables y renombramos todas las columnas que contienen el patrón _% eliminando ese patrón del nombre de la columna

```{r echo=TRUE,eval=FALSE}
datos_modelo_reg=datos %>%
  select(-c("released_year",
            "released_month",
            "released_day",
            "track_name", 
            "artist(s)_name")) %>% 
  rename_with(~ str_replace(., "_%", ""), contains("_%"))
```

División de datos en conjuntos de entrenamiento y prueba

```{r echo=TRUE,eval=FALSE}
particion_reg = datos_modelo_reg %>% 
  initial_split(prop = 0.8)

datos_reg_ent= particion_reg %>% training()

datos_reg_test= particion_reg %>% testing()
```

Creamos una receta de preprocesamiento de datos

```{r echo=TRUE,eval=FALSE}
preprocesado_reg=recipe(streams ~ .,datos_modelo_reg) %>%
  step_novel(all_nominal()) %>%
  step_dummy(all_nominal()) %>%
  step_zv(all_predictors()) %>% 
  step_normalize(all_predictors())
```

Creamos un objeto para realizar validación cruzada

```{r echo=TRUE}
cv_folds_reg=vfold_cv(datos_reg_ent, v = 10)
```

## Col

### Streams

```{r}
grafico_streams=ggplot(datos_modelo_reg, aes(x = streams)) +
  geom_density(color = "blue", fill = "lightblue", alpha = 0.4) +
  labs(title = "",
       x = "Streams",
       y = "") +
  theme_minimal()

ggplotly(grafico_streams)
```


# Regresión lineal {data-navmenu="Regresión"}

## Col

### Regresión lineal

Especificamos el tipo de modelos que queremos usar junto con el motor

```{r echo=TRUE,eval=FALSE}
lm_reg_spec=linear_reg() %>% 
  set_engine("lm") %>% 
  set_mode("regression")
```

Creamos un workflow, añadiendole el modelo y el preprocesado

```{r echo=TRUE, eval=FALSE}
lm_reg_wf=
  workflow() %>% 
  add_model(lm_reg_spec) %>% 
  add_recipe(preprocesado_reg) 
```

Ajusta el flujo de trabajo utilizando validación cruzada con los folds definidos anteriormente

```{r echo=TRUE,eval=FALSE}
resultados_lm_reg_cv = lm_reg_wf %>% 
  fit_resamples(
    resamples = cv_folds_reg
  )
```

"Recogemos" las metricas de la validación cruzada

```{r echo=TRUE}
resultados_lm_reg_cv %>%
  collect_metrics()
```

Validamos nuestro modelo con nuestro conjunto de prueba

```{r echo=TRUE,eval=FALSE}
modelo_lm_reg=lm_reg_wf %>%
  last_fit(particion_reg)
```

```{r echo=TRUE}
modelo_lm_reg %>% 
  collect_metrics()
```

## Col

### Importancia de las variables

```{r}
library(vip)
modelo_lm_reg %>% 
  extract_fit_engine() %>%
  vip()
```

# Bosques aleatorios de regresión {data-navmenu="Regresión"}

## Col

### Bosques aleatorios de regresión

Especificación para un modelo de bosque aleatorio donde los hiperparámetros que se configuran para ser ajustados mediante `tune()`

```{r echo=TRUE,eval=FALSE}
rf_reg_spec=rand_forest(trees=tune(),
                    min_n=tune()) %>% 
  set_engine("randomForest") %>% 
  set_mode("regression")
```

Creamos el workflow

```{r echo=TRUE,eval=FALSE}
rf_reg_wf=
  workflow() %>% 
  add_model(rf_reg_spec) %>% 
  add_recipe(preprocesado_reg) 
```

Creamos una cuadrícula de hiperparámetros para el tune()

```{r echo=TRUE,eval=FALSE}
grid_rf_reg = expand_grid(trees=seq(100,200,by=50),
                           min_n=seq(2,8,by=2))
```

Ajustamos el flujo de trabajo utilizando validación cruzada con los folds definidos anteriormente

```{r echo=TRUE,eval=FALSE}
resultados_tune_rf_reg = rf_reg_wf %>% 
  tune_grid(
    resamples = cv_folds_reg,
    grid = grid_rf_reg)
```

```{r echo=TRUE}
resultados_tune_rf_reg %>%
  collect_metrics()
```

Seleccionamos la combinación de hiperparámetros que maximiza el coeficiente de determinación (el mejor modelo)

```{r echo=TRUE,eval=FALSE}
rf_reg_mejor=resultados_tune_rf_reg %>%
  select_best(metric="rsq")
```

Actualizamos el flujo de trabajo con los mejores hiperparámetros seleccionados

```{r echo=TRUE,eval=FALSE}
rf_reg_wfl_final = rf_reg_wf %>% 
  finalize_workflow(rf_reg_mejor)
```

Validamos nuestro modelo con nuestro conjunto de prueba

```{r echo=TRUE,eval=FALSE}
modelo_rf_reg_final = rf_reg_wfl_final %>%
  last_fit(particion_reg)
```

```{r echo=TRUE}
modelo_rf_reg_final %>% 
  collect_metrics() 
```

## Col

### Importancia de las variables

```{r}
modelo_rf_reg_final %>% 
  extract_fit_engine() %>%
  vip()
```


# Máquinas de soporte vectorial {data-navmenu="Regresión"}

## Col

### Máquinas de soporte vectorial

```{r echo=TRUE,eval=FALSE}
svm_reg_spec=svm_rbf(cost=tune(),rbf_sigma=tune()) %>% 
  set_engine("kernlab") %>% 
  set_mode("regression")

svm_reg_wf=
  workflow() %>% 
  add_model(svm_reg_spec) %>% 
  add_recipe(preprocesado_reg) 

grid_svm_reg = expand_grid(cost = 3^(1:4), rbf_sigma = 3^(-5:-2))

resultados_tune_svm_reg = svm_reg_wf %>% 
  tune_grid(
    resamples = cv_folds_reg,
    grid = grid_svm_reg)
```

```{r echo=TRUE}
resultados_tune_svm_reg %>%
  collect_metrics()
```

```{r echo=TRUE,eval=FALSE}
svm_reg_mejor=resultados_tune_svm_reg %>%
  select_best(metric="rsq")

svm_reg_wfl_final = svm_reg_wf %>% 
  finalize_workflow(svm_reg_mejor)

modelo_svm_reg_final = svm_reg_wfl_final %>%
  last_fit(particion_reg)
```

```{r echo=TRUE}
modelo_svm_reg_final %>% 
  collect_metrics() 
```

# Redes neuronales {data-navmenu="Regresión"}

## Col

### Redes neuronales

Véase el `RedesNeuronales_reg.ipynb`, en la carpeta modelos > regresion.

# Elementos comunes {data-navmenu="Clasificación"}

## Col


### Elementos comunes

```{r}
load("complementario/Modelos_class.RData")
```

```{r echo=TRUE,eval=FALSE}
datos_modelo_class = datos %>%
  rename_with(~ str_replace(., "_%", ""), contains("_%")) %>%
  select(-c(1:16))

particion_class = initial_split(datos_modelo_class, prop = 0.8)

datos_class_ent = training(particion_class)

datos_class_test = testing(particion_class)

preprocesado_class = recipe(mode ~ ., datos_modelo_class) %>%
  step_zv(all_numeric()) %>%
  step_normalize(all_numeric())

cv_folds_class = vfold_cv(datos_class_ent, v = 10, strata = mode)
```

### Clases balanceadas

```{r}
datos %>%
  count(mode) %>% 
  hchart("column", hcaes(x = mode, y = n)) %>%
  hc_title(text = "") %>%
  hc_subtitle(text = "") %>%
  hc_yAxis(title = list(text = "")) %>%
  hc_xAxis(title = list(text = "")) %>%
  hc_legend(enabled = FALSE) %>%
  hc_chart(zoomType = "x") %>%
  hc_colors(c("#7CB5EC")) 
```

## Col

### Gráfico multivariante de las variables

```{r}
library(GGally)
datos_modelo_class %>% 
  select(-mode) %>% 
  ggpairs(
        lower = list(continuous = wrap("points", alpha = 0.2, color = "blue")),
        diag = list(continuous = wrap("barDiag", fill = "blue")))
```


# Regresión logística {data-navmenu="Clasificación"}

## Col

### Regresión logística

```{r echo=TRUE,eval=FALSE}
log_class_spec = logistic_reg() %>%
  set_engine("glm") %>%
  set_mode("classification")

log_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(log_class_spec)

resultados_log_class_cv = log_class_wfl %>% 
  fit_resamples(
    resamples = cv_folds_class,
    metrics = metric_set(roc_auc, accuracy)
    )
```

```{r echo=TRUE}
resultados_log_class_cv %>%
  collect_metrics()
```

```{r echo=TRUE,eval=FALSE}
modelo_log_class_final=log_class_wfl %>% 
  last_fit(particion_class)
```

```{r echo=TRUE}
modelo_log_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)

modelo_log_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
```

## Col

### Importancia de las variables

```{r}
modelo_log_class_final %>% 
  extract_fit_engine() %>%
  vip()
```

### Curva ROC

```{r}
modelo_log_class_final %>%
  collect_predictions() %>% 
  roc_curve(truth = mode, .pred_Major) %>% 
  autoplot
```

# Máquinas de soporte vectorial {data-navmenu="Clasificación"}

## Col

### Máquinas de soporte vectorial

```{r echo=TRUE,eval=FALSE}
svm_class_spec = svm_rbf(cost = tune(), rbf_sigma = tune()) %>%
  set_engine("kernlab") %>%
  set_mode("classification")

svm_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(svm_class_spec)

grid_svm_class = expand_grid(cost = 10^seq(-3, 2, length.out = 5), 
                             rbf_sigma = 10^seq(-3, 0, length.out = 5))

resultados_svm_class_tune = svm_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_svm_class,
    metrics = metric_set(roc_auc, accuracy)
  )
```

```{r echo=TRUE}
resultados_svm_class_tune %>%
  collect_metrics()
```

```{r echo=TRUE,eval=FALSE}
svm_class_mejor=resultados_svm_class_tune %>%
  select_best(metric="accuracy")

svm_class_wfl_final = svm_class_wfl %>% 
  finalize_workflow(svm_class_mejor)

modelo_svm_class_final = svm_class_wfl_final %>%
  last_fit(particion_class)
```

```{r echo=TRUE}
modelo_svm_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
  
modelo_svm_class_final %>% 
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
```

```{r}
modelo_svm_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
```

## Col

### Curva ROC

```{r}
modelo_svm_class_final %>%
  collect_predictions() %>% 
  roc_curve(truth = mode, .pred_Major) %>% 
  autoplot
```

# Bosques aleatorios de clasificación {data-navmenu="Clasificación"}

## Col

### Bosques aleatorios de clasificación

```{r echo=TRUE,eval=FALSE}
rf_class_spec = rand_forest(
  mtry = tune(),
  min_n = tune(),
  trees = 1000  # Número de árboles fijo
) %>%
  set_engine("randomForest") %>%
  set_mode("classification")

rf_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(rf_class_spec)

grid_rf_class = expand_grid(
  mtry = seq(2, ncol(datos_class_ent) - 1, by = 2), 
  min_n = seq(2, 22, by = 4))

resultados_tune_rf_class = rf_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_rf_class,
    metrics = metric_set(roc_auc, accuracy)
)
```

```{r echo=TRUE}
resultados_tune_rf_class %>%
  collect_metrics()
```

```{r echo=TRUE,eval=FALSE}
rf_class_mejor=resultados_tune_rf_class %>%
  select_best(metric="accuracy")

rf_class_wfl_final = rf_class_wfl %>% 
  finalize_workflow(rf_class_mejor)

modelo_rf_class_final = rf_class_wfl_final %>%
  last_fit(particion_class)
```

```{r echo=TRUE}
modelo_rf_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)

modelo_rf_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)
```

## Col

### Importancia de las variables

```{r}
modelo_rf_class_final %>% 
  extract_fit_engine() %>%
  vip()
```

### Curva ROC

```{r}
modelo_rf_class_final %>%
  collect_predictions() %>% 
  roc_curve(truth = mode, .pred_Major) %>% 
  autoplot
```

# K-nearest neighbors {data-navmenu="Clasificación"}

## Col

### K-nearest neighbors

```{r echo=TRUE,eval=FALSE}
knn_class_spec = nearest_neighbor(
  neighbors = tune(),       
  weight_func = tune(),     
  dist_power = tune()       
) %>%
  set_engine("kknn") %>%
  set_mode("classification")

knn_class_wfl = workflow() %>%
  add_recipe(preprocesado_class) %>%
  add_model(knn_class_spec)

grid_knn_class = expand_grid(
  neighbors = seq(1, 20, by = 2),
  weight_func = c("gaussian", "optimal"),
  dist_power = 1:2
)

resultados_tune_knn_class = knn_class_wfl %>% 
  tune_grid(
    resamples = cv_folds_class,
    grid = grid_knn_class,
    metrics = metric_set(roc_auc, accuracy)
)
```

```{r echo=TRUE}
resultados_tune_knn_class %>%
  collect_metrics()
```

```{r echo=TRUE,eval=FALSE}
knn_class_mejor=resultados_tune_knn_class %>%
  select_best(metric="accuracy")

knn_class_wfl_final = knn_class_wfl %>% 
  finalize_workflow(knn_class_mejor)

modelo_knn_class_final = knn_class_wfl_final %>%
  last_fit(particion_class)
```

```{r echo=TRUE}
modelo_knn_class_final %>% 
  collect_predictions() %>% 
  conf_mat(truth = mode, estimate = .pred_class)

modelo_knn_class_final %>%
  collect_predictions() %>% 
  metrics(truth = mode, estimate = .pred_class)
```

## Col

### Curva ROC

```{r}
modelo_knn_class_final %>%
  collect_predictions() %>% 
  roc_curve(truth = mode, .pred_Major) %>% 
  autoplot
```